#!/usr/bin/env ruby

# Copyright (c) 2010-2012 Microsoft Corp.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#	http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

require 'rubygems'
require 'ruby-aws'
require 'optparse'
require 'digest/md5'
require 'rexml/document'

require './include/array'
require './include/assignment'
require './include/assignment_set'
require './include/config'
require './include/proxy'
require './include/requester'
require './include/templates'

@config = MOS::Config.create
@mturk = Amazon::WebServices::MechanicalTurkRequester.new :Host => @config[:Server]

@options = { }
optparse = OptionParser.new do |opts|
	opts.banner = "Usage: #{$0} [options]"
 
	@options[:commit] = false
	opts.on( '-c', '--commit', 'Effectively grant bonuses, committing results to the Mechanical Turk server' ) do
		@options[:commit] = true
	end

	@options[:quiet] = false
	opts.on( '-q', '--quiet', 'Do not print any messages (implies -f)' ) do
		@options[:quiet] = true
	end

	@options[:force] = false
	opts.on( '-f', '--force', 'Do not ask for confirmation' ) do
		@options[:force] = true
	end
 
	opts.on( '-h', '--help', 'Display this screen' ) do
		puts opts
		exit
	end

	opts.on_tail("")
	opts.on_tail("This script automatically analyzes assignments and grant bonuses to workers.")
	opts.on_tail("Before running this script, use GetAssignments.rb to collect submitted assignments")
	opts.on_tail("and AnalyzeAssignments.rb to approve them.")
end

begin
	optparse.parse!
rescue OptionParser::ParseError => e
	$stderr.puts("Error parsing command-line arguments: " + e.message)
	$stderr.puts("Try '#{$0} --help' for more information.")
	exit
end

if @options[:quiet]
	@options[:force] = true
	devnull = File.exist?('/dev/null') ? '/dev/null' : 'NUL'
	$stdout = File.new(devnull, 'w')
end

if @options[:commit] && !@options[:force]
	printf("Are you sure you want to grant bonuses for all approved assignments? (y/n) ")
	response = STDIN.gets.chomp.downcase
	if response != 'y'
		printf("Exiting...\n")
		exit
	end
end

input = Amazon::Util::DataReader.load(@config[:HITInputFile], :Tabular)
input.each do |line|
	line[:sentence]   = line[:sentence].to_s
	line[:algorithm]  = line[:algorithm].to_s
	line[:identifier] = line[:sentence] + '_' + line[:algorithm]
end
input.sort!{ |a,b| a[:identifier] <=> b[:identifier] }
input.each_index { |i| input[i][:identifier] = 'ID' + i.to_s } if @config[:RewriteIdentifier]
input = MOS.indexByUniqueKey(input, :identifier)

if !File.exist?(@config[:HITResultsFile])
	$stderr.puts("No results file to analyze. Please run GetAssignments.rb first.")
	exit
end

results = Amazon::Util::DataReader.load(@config[:HITResultsFile], :Tabular)
assignment_set = results.clone
assignment_set.map! { |a| MOS::Assignment.new(a.clone, input) }
assignment_set = MOS::AssignmentSet.new(assignment_set)

fast_workers = assignment_set.fast_workers(@config[:SamplesPerHIT] * @config[:MinSampleWorkingTime])
bad_workers  = assignment_set.bad_workers(@config[:BonusMinAssignments])

results.delete_if { |a| fast_workers[a[:WorkerId].to_sym] }
results.delete_if { |a| bad_workers [a[:WorkerId].to_sym] }
assignment_set = results.clone
assignment_set.map! { |a| MOS::Assignment.new(a.clone, input) }
assignment_set = MOS::AssignmentSet.new(assignment_set)

bonus = assignment_set.compute_bonuses(@config[:BonusMinAssignments],
				       @config[:SamplesPerHIT] * @config[:MinSampleWorkingTime],
				       @config[:BonusForQuantity],
				       @config[:BonusForQualityTop50Pct],
				       @config[:BonusForQualityTop10Pct])

total_cost = 0.0
bonus.each_pair do |worker_id, b|
	next if b[:amount] == 0.0

	total = 0.0
	results.each do |a|
		if a[:WorkerId] == worker_id.to_s && !a[:Bonus]
			total += b[:amount]
		end
	end
	total_cost += total
end
total_cost *= 1.10	# Amazon's commission is 10%

available_funds = @mturk.availableFunds
if available_funds < total_cost && @options[:commit]
	$stderr.printf("Not enough funds: operation requires %.2f USD, account contains %.2f USD.\n",
			total_cost, available_funds)
	exit
end

bonus.each_pair do |worker_id, b|
	next if b[:amount] == 0.0

	subject  = "Bonus summary from #{@config[:RequesterName]}"
	message  = "You have been granted the following bonuses:\n\n"
	message += sprintf("%-30s %10s %-40s\n", "Assignment Id", "Amount", "Reason")

	total = 0.0
	results.each do |a|
		if a[:WorkerId] == worker_id.to_s && !a[:Bonus]
			amountstr = sprintf("%.2f USD", b[:amount])
			message += sprintf("%-30s %10s %-40s\n", a[:AssignmentId], amountstr, b[:reason])
			total += b[:amount]
		end
	end
	next if total == 0.0

	message += "\n"
	message += sprintf("Total amount: %.2f USD\n", total)
	message += sprintf("Your Worker Id: %s\n", worker_id.to_s)

	message += "\n"
	message += "Thank you,\n\n"
	message += "#{@config[:RequesterName]}\n"

	puts("-" * 80)
	puts("When committing, following message will be sent to worker #{worker_id}") if !@options[:commit]
	puts("Sending the following message to worker #{worker_id}") if  @options[:commit]
	puts("")
	puts(message)
	puts("-" * 80)

	if @options[:commit]
		begin
			@mturk.email_workers([worker_id], subject, message)
			printf("Successfully e-mailed bonus summary to Worker %s.\n", worker_id)
		rescue => e
			$stderr.puts("Error emailing worker: " + e.message)
		end
	end
end

error_count = 0
total_granted = 0.0
results.each do |a|
	worker_id = a[:WorkerId]
	assignment_id = a[:AssignmentId]
	amount = bonus[worker_id.to_sym][:amount]
	reason = bonus[worker_id.to_sym][:reason]

	if amount > 0.0 && !a[:Bonus]
		begin
			if @options[:commit]
				@mturk.grant_bonus(worker_id, assignment_id, amount, reason)
				printf("Successfully granted bonus to Worker %s.\n", worker_id)
				a[:Bonus] = amount
				total_granted += amount
			end
		rescue => e
			$stderr.puts("Error granting bonus: " + e.message)
			error_count += 1
		end
	end
end
total_granted *= 1.10	# Amazon's commission is 10%

printf("Total amount of granted bonuses: %.2f out of %.2f USD\n", total_granted, total_cost)
printf("Could not grant %d bonuses due to the errors printed above. Please retry.\n", error_count) if error_count > 0

if @options[:commit]
	if File.exist?(@config[:HITResultsFile])
		FileUtils.move(@config[:HITResultsFile], @config[:HITResultsFile] + '.backup')
	end
	Amazon::Util::DataReader.save(@config[:HITResultsFile], results, :Tabular)
end

